package com.yiduo.outpost.core.security.config;

import com.yiduo.outpost.core.feign.config.OpenFeignAutoConfiguration;
import com.yiduo.outpost.core.security.SecurityProperties;
import com.yiduo.outpost.core.security.TokenProvider;
import com.yiduo.outpost.core.security.jwt.JwtAutoConfiguration;
import com.yiduo.outpost.core.security.jwt.JwtFilter;
import com.yiduo.outpost.core.security.jwt.JwtTokenGenerator;
import com.yiduo.outpost.core.security.remote.UCenterRemote;
import com.yiduo.outpost.core.model.constant.SecurityConstant;
import com.yiduo.outpost.core.model.entity.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.filter.CorsFilter;

import java.io.IOException;
import java.util.Collections;

/**
 * Security auto configuration support.
 *
 * @author yw
 */
@Configuration
@ConditionalOnClass(AbstractSecurityWebApplicationInitializer.class)
//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnProperty(name = "com.yiduo.outpost.security.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfigureAfter({JwtAutoConfiguration.class})
public class SecurityAutoConfiguration {

    @Autowired
    private TokenProvider tokenProvider;

    @Autowired
    private ObjectProvider<CorsFilter> corsFilterObjectProvider;

    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                // 允许跨域请求的OPTIONS请求
                .requestMatchers(HttpMethod.OPTIONS, "/**")
                // 允许匿名访问的所有路由
                .requestMatchers(securityProperties.getPermitUrls());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

        corsFilterObjectProvider.ifAvailable(corsFilter -> http.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class));
        JwtFilter jwtFilter = new JwtFilter(tokenProvider, jwtTokenGenerator);

        HttpSecurity httpSecurity = http
                .csrf().disable()
                .httpBasic().disable()
                .exceptionHandling()
                .authenticationEntryPoint(new UnAuthorizedEntryPoint())
                .accessDeniedHandler(new AccessDeniedHandlerImpl())
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                        .anyRequest().authenticated()
                )
                // 添加JWT filter 用于验证其他请求的Token是否合法
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }

    @Autowired
    private JwtTokenGenerator jwtTokenGenerator;

    /**
     * UnAuthorized entry point.
     * security 未登录异常处理
     */
    public static class UnAuthorizedEntryPoint implements AuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "未登录!");
        }

    }

    /**
     * JWT token provider auto configuration.
     */
    @Configuration
    @EnableFeignClients(basePackages = "com.yiduo.outpost.core.security.remote")
    @ConditionalOnMissingBean(TokenProvider.class)
    @AutoConfigureAfter(OpenFeignAutoConfiguration.class)
    public static class JwtTokenProviderAutoConfiguration {

        @Bean
        public TokenProvider jwtTokenProvider(UCenterRemote uCenterRemote) {
            return token -> {
                User user = uCenterRemote.fetchByToken(SecurityConstant.BEARER_PREFIX + token);
//                User user = new User();
                if (user == null) {
                    return new AnonymousAuthenticationToken(token, token, Collections.emptyList());
                }
                return new UsernamePasswordAuthenticationToken(user, token, user.getAuthorities());
            };
        }
    }
}
